import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { GitService } from '../git/GitService';

export class WebviewManager {
    private static currentPanel: WebviewManager | undefined;
    private readonly _panel: vscode.WebviewPanel;
    private readonly _extensionUri: vscode.Uri;
    private _disposables: vscode.Disposable[] = [];
    private readonly _gitService: GitService;

    public static createOrShow(extensionUri: vscode.Uri, gitService: GitService) {
        const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;

        if (WebviewManager.currentPanel) {
            WebviewManager.currentPanel._panel.reveal(column);
            return;
        }

        const panel = vscode.window.createWebviewPanel(
            'gitCommanderPro',
            'Git Commander Pro',
            column || vscode.ViewColumn.One,
            {
                enableScripts: true,
                localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'src', 'webview')],
            }
        );

        WebviewManager.currentPanel = new WebviewManager(panel, extensionUri, gitService);
    }

    private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, gitService: GitService) {
        this._panel = panel;
        this._extensionUri = extensionUri;
        this._gitService = gitService;

        // Set the webview's initial html content
        this._panel.webview.html = this._getHtmlForWebview();

        this._panel.onDidDispose(() => this.dispose(), null, this._disposables);

        this._panel.webview.onDidReceiveMessage(
            async message => {
                switch (message.command) {
                    case 'webviewReady':
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        return;
                    case 'getStatus':
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        return;
                    case 'getHistory':
                        this.postMessage('updateHistory', await this._gitService.getHistory());
                        return;
                    case 'getBranches':
                        this.postMessage('updateBranches', await this._gitService.getBranches());
                        return;
                    case 'getStashList':
                        this.postMessage('updateStashList', await this._gitService.getStashList() as any);
                        return;
                    case 'viewDiff':
                        this.handleViewDiff(message.payload);
                        return;
                    case 'discardChanges':
                        await this._gitService.discardChanges(message.payload.path);
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        return;
                    case 'commit':
                        await this.handleCommit(message.payload.message);
                        return;
                    case 'commitAndPush':
                        await this.handleCommitAndPush(message.payload.message);
                        return;
                    case 'stashAll':
                        const statusForStash = await this._gitService.getStatus();
                        if (statusForStash.files.length === 0) {
                            vscode.window.showInformationMessage('Working directory is clean. Nothing to stash.');
                            return;
                        }
                        await this._gitService.createStash(message.payload.message);
                        const status = await this._gitService.getStatus();
                        const stashList = await this._gitService.getStashList();
                        this.postMessage('updateStatus', status);
                        this.postMessage('updateStashList', stashList as any);
                        return;
                    case 'discardAll':
                        const statusForDiscard = await this._gitService.getStatus();
                        if (statusForDiscard.files.length === 0) {
                            vscode.window.showInformationMessage('Working directory is clean. Nothing to discard.');
                            return;
                        }
                        await this._gitService.discardAllChanges();
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        return;
                    case 'resetToCommit':
                        await this._gitService.resetToCommit(message.payload.hash);
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        this.postMessage('updateHistory', await this._gitService.getHistory());
                        return;
                    case 'createTag':
                        await this._gitService.createTagForCommit(message.payload.hash);
                        this.postMessage('updateHistory', await this._gitService.getHistory());
                        return;
                    case 'checkoutBranch':
                        await this._gitService.switchBranch(message.payload.branchName);
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        this.postMessage('updateBranches', await this._gitService.getBranches());
                        return;
                    case 'mergeBranch':
                        await this._gitService.mergeBranch(message.payload.branchName);
                        this.postMessage('operationSuccess', {
                            status: await this._gitService.getStatus(),
                            history: await this._gitService.getHistory(),
                            branches: await this._gitService.getBranches(),
                            activeTab: 'workspace'
                        });
                        return;
                    case 'pull':
                        await this._gitService.pull();
                        this.postMessage('updateStatus', await this._gitService.getStatus());
                        this.postMessage('updateHistory', await this._gitService.getHistory());
                        return;
                    case 'push':
                        await this._gitService.push();
                        this.postMessage('updateHistory', await this._gitService.getHistory());
                        this.postMessage('updateBranches', await this._gitService.getBranches());
                        return;
                    case 'deleteLocalBranch':
                        await this._gitService.deleteLocalBranch(message.payload.branchName);
                        this.postMessage('updateBranches', await this._gitService.getBranches());
                        return;
                    case 'deleteRemoteBranch':
                        await this._gitService.deleteRemoteBranch(message.payload.branchName);
                        this.postMessage('updateBranches', await this._gitService.getBranches());
                        return;
                    case 'createBranch':
                        const newBranchName = await vscode.window.showInputBox({ prompt: 'Enter the name for the new branch' });
                        if (newBranchName) {
                            await this._gitService.createBranch(newBranchName);
                            this.postMessage('updateBranches', await this._gitService.getBranches());
                        }
                        return;
                    case 'popStash':
                        await this._gitService.popStash(message.payload.stashRef);
                        this.postMessage('operationSuccess', {
                            status: await this._gitService.getStatus(),
                            history: await this._gitService.getHistory(),
                            branches: await this._gitService.getBranches(),
                            stashes: await this._gitService.getStashList() as any,
                            activeTab: 'workspace'
                        });
                        return;
                    case 'dropStash':
                        await this._gitService.dropStash(message.payload.stashRef);
                        this.postMessage('updateStashList', await this._gitService.getStashList() as any);
                        return;
                }
            },
            null,
            this._disposables
        );
    }
    
    public postMessage(command: string, payload: any) {
        this._panel.webview.postMessage({ command, payload });
    }
    
    private async handleCommit(message: string) {
        if (!message.trim()) {
            vscode.window.showInformationMessage('Commit message cannot be empty.');
            return;
        }
        const status = await this._gitService.getStatus();
        if (status.files.length === 0) {
            vscode.window.showInformationMessage('Working directory is clean. Nothing to commit.');
            return;
        }
        try {
            await this._gitService.commit(message);
            this.postMessage('updateStatus', await this._gitService.getStatus());
            this.postMessage('commitDone', {});
        } catch (e) {
            console.error(e);
        }
    }
    
    private async handleCommitAndPush(message: string) {
        if (!message.trim()) {
            vscode.window.showInformationMessage('Commit message cannot be empty.');
            return;
        }
        const status = await this._gitService.getStatus();
        if (status.files.length === 0) {
            vscode.window.showInformationMessage('Working directory is clean. Nothing to commit.');
            return;
        }
        try {
            await this._gitService.commitAndPush(message);
            this.postMessage('updateStatus', await this._gitService.getStatus());
            this.postMessage('commitDone', {});
        } catch (e) {
            console.error(e);
        }
    }

    private async handleViewDiff(payload: {path: string, status: string}) {
         const workspaceFolders = vscode.workspace.workspaceFolders;
         if (!workspaceFolders) return;
    
         const file = { path: payload.path, working_dir: payload.status };
         
         const repoRoot = await this._gitService.getRepoRoot();
         const absoluteFilePath = vscode.Uri.file(path.join(repoRoot, file.path));
         
         let leftUri: vscode.Uri;
         const diffTitle = `${file.path} (Working Tree)`;
    
         if (file.working_dir === '?') {
             leftUri = vscode.Uri.parse('untitled:' + absoluteFilePath.fsPath);
         } else {
            // This is the magic combination that finally works.
            // The main URI path must be the absolute path to the file.
            // The query must be a JSON object containing `path` (absolute) and `ref`.
            const query = {
                path: absoluteFilePath.fsPath, // ABSOLUTE path
                ref: 'HEAD'
            };
            leftUri = absoluteFilePath.with({
                scheme: 'git',
                query: JSON.stringify(query)
            });
         }
         vscode.commands.executeCommand('vscode.diff', leftUri, absoluteFilePath, diffTitle);
    }

    public dispose() {
        WebviewManager.currentPanel = undefined;
        this._panel.dispose();
        while (this._disposables.length) {
            const x = this._disposables.pop();
            if (x) {
                x.dispose();
            }
        }
    }

    private _getHtmlForWebview(): string {
        const webview = this._panel.webview;
        const nonce = getNonce();

        const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'src', 'webview', 'main.js'));
        const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'src', 'webview', 'style.css'));

        const htmlPath = vscode.Uri.joinPath(this._extensionUri, 'src', 'webview', 'index.html');
        let html = fs.readFileSync(htmlPath.fsPath, 'utf8');

        html = html.replace(/{{cspSource}}/g, webview.cspSource);
        html = html.replace(/{{nonce}}/g, nonce);
        html = html.replace('{{styleUri}}', styleUri.toString());
        html = html.replace('{{scriptUri}}', scriptUri.toString());
        
        return html;
    }
}

function getNonce() {
    let text = '';
    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (let i = 0; i < 32; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
} 